package com.atguigu.gmall.search.service;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.pms.entity.BrandEntity;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import com.atguigu.gmall.search.pojo.Goods;
import com.atguigu.gmall.search.pojo.SearchParmVo;
import com.atguigu.gmall.search.pojo.SearchResponseAttrVo;
import com.atguigu.gmall.search.pojo.SearchResponseVo;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class SearchService {
    @Autowired
    private RestHighLevelClient restHighLevelClient;

    public SearchResponseVo search(SearchParmVo searchParmVo) {

        try {//new SearchRequest  参数一  查询的索引库   参数二  查询条件
            SearchResponse response = restHighLevelClient.search(new SearchRequest(new String[]{"goods"}, this.buildDsl(searchParmVo)), RequestOptions.DEFAULT);

            //todo 解析结果集 即获得响应数据
            SearchResponseVo searchResponseVo = this.parseResult(response);
            // 分页参数从参数对象中获取
            searchResponseVo.setPageNum(searchParmVo.getPageNum());
            searchResponseVo.setPageSize(searchParmVo.getPageSize());

            return searchResponseVo;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 解析结果集
     */
    private SearchResponseVo parseResult(SearchResponse response) {
        SearchResponseVo searchResponseVo = new SearchResponseVo();

        //解析普通搜索结果集获取分页类型参数
        SearchHits hits = response.getHits();
        long value = hits.getTotalHits().value;
        searchResponseVo.setTotal(value); //设置总记录数

        //获取hits下的hits数组,就是当页数据
        SearchHit[] hitsHits = hits.getHits();
        //把hitsHits 数组 转化成goods集合
        searchResponseVo.setGoodsList(Arrays.stream(hitsHits).map(hitsHit -> {
            //获取_souurce 的json字符串,并反序列化为 goods对象
            String sourceAsString = hitsHit.getSourceAsString();
            Goods goods = JSON.parseObject(sourceAsString, Goods.class);
            //响应数据里面title并没有高亮,因此在获取高亮结果集中的标题,并覆盖原title数据
            Map<String, HighlightField> highlightFields = hitsHit.getHighlightFields();
            if (CollectionUtils.isEmpty(highlightFields)) {
                return goods;
            }
            HighlightField highlightField = highlightFields.get("title");
            if (highlightField == null || highlightField.getFragments().length == 0) {
                return goods;
            }
            Text fragment = highlightField.getFragments()[0];
            goods.setTitle(fragment.string());
            return goods;
        }).collect(Collectors.toList()));


        //解析聚合结果集获取过滤列表
        Aggregations aggregations = response.getAggregations();

        //获取品牌id的集合
        ParsedLongTerms brandIdAgg = aggregations.get("brandIdAgg");
        //获取品牌id聚合里的桶
        List<? extends Terms.Bucket> brandIdBuckets = brandIdAgg.getBuckets();

        //把品牌id集合的桶集合  转换成  品牌集合
        if (!CollectionUtils.isEmpty(brandIdBuckets)) {
            searchResponseVo.setBrands(brandIdBuckets.stream().map(bucket -> {
                BrandEntity brandEntity = new BrandEntity();
                //当前桶中得key就是品牌id
                brandEntity.setId(bucket.getKeyAsNumber().longValue());
                //获取同种的自聚合(包含了brandNameAgg 和 logoAgg
                Aggregations subAggs = bucket.getAggregations();

                //获取自聚合中的品牌名称的子聚合
                ParsedStringTerms brandNameAgg = subAggs.get("brandNameAgg");
                //子聚合内 对应的桶 有且只有一个,所以获取 第一个数据 就是对应的品牌的名称
                List<? extends Terms.Bucket> brandNameAggBuckets = brandNameAgg.getBuckets();
                if (!CollectionUtils.isEmpty(brandNameAggBuckets)) {
                    brandEntity.setName(brandNameAggBuckets.get(0).getKeyAsString());
                }

                //获取品牌的logo的子聚合
                ParsedStringTerms logoAgg = subAggs.get("logoAgg");
                //子聚合内 对应的桶 有且只有一个,所以获取 第一个数据 就是对应的品牌的名称
                List<? extends Terms.Bucket> logoAggBuckets = logoAgg.getBuckets();
                if (!CollectionUtils.isEmpty(logoAggBuckets)) {
                    brandEntity.setLogo(logoAggBuckets.get(0).getKeyAsString());
                }

                return brandEntity;
            }).collect(Collectors.toList()));
        }

        // 获取分类id聚合
        ParsedLongTerms categoryIdAgg = aggregations.get("categoryIdAgg");
        // 获取分类id聚合中的桶
        List<? extends Terms.Bucket> categoryIdBuckets = categoryIdAgg.getBuckets();
        // 把桶集合转化成 分类对象集合
        if (!CollectionUtils.isEmpty(categoryIdBuckets)) {
            searchResponseVo.setCategories(categoryIdBuckets.stream().map(bucket -> {
                CategoryEntity categoryEntity = new CategoryEntity();
                categoryEntity.setId(bucket.getKeyAsNumber().longValue());
                //获取桶中的分类名称子聚合
                ParsedStringTerms categoryNameAgg = bucket.getAggregations().get("categoryNameAgg");
                //获取分类名称子聚合 中的桶
                List<? extends Terms.Bucket> nameAggBuckets = categoryNameAgg.getBuckets();
                if (!CollectionUtils.isEmpty(nameAggBuckets)) {
                    categoryEntity.setName(nameAggBuckets.get(0).getKeyAsString());
                }

                return categoryEntity;
            }).collect(Collectors.toList()));
        }

        //获取规格参数聚合  嵌套聚合函数,下面还有聚合和子聚合
        ParsedNested attrAgg = aggregations.get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        List<? extends Terms.Bucket> attrIdBuckets = attrIdAgg.getBuckets();
        //把规格参数id桶集合 转化成 SearchResponseAttrVo集合
        if (!CollectionUtils.isEmpty(attrIdBuckets)) {
            searchResponseVo.setFilters(attrIdBuckets.stream().map(bucket -> {
                SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();

                //获取当前桶中的key 就是规格参数的id
                searchResponseAttrVo.setAttrId(bucket.getKeyAsNumber().longValue());
                //获取桶中的子聚合 attrNameAgg 同样自聚合内的桶中有且只有一个数据
                ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
                List<? extends Terms.Bucket> nameAggBuckets = attrNameAgg.getBuckets();
                if (!CollectionUtils.isEmpty(nameAggBuckets)){
                    searchResponseAttrVo.setAttrName(nameAggBuckets.get(0).getKeyAsString());
                }

                // 获取规格参数值的子聚合
                ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
                // 规格参数值的桶集合
                List<? extends Terms.Bucket> buckets = attrValueAgg.getBuckets();
                if (!CollectionUtils.isEmpty(buckets)){
                    searchResponseAttrVo.setAttrValues(buckets.stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList()));
                }

                return searchResponseAttrVo;
            }).collect(Collectors.toList()));
        }

        return searchResponseVo;
    }

    /**
     * 构建DSL语句的私有方法
     *
     * @param searchParmVo
     * @return
     */
    private SearchSourceBuilder buildDsl(SearchParmVo searchParmVo) {
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        String keyword = searchParmVo.getKeyword();
        if (StringUtils.isBlank(keyword)) {
            throw new RuntimeException("搜索关键字不能为空");
        }

        //1.查询过滤条件
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        searchSourceBuilder.query(boolQueryBuilder);
        //1.1构建匹配查询
        boolQueryBuilder.must(QueryBuilders.matchQuery("title", keyword).operator(Operator.AND));

        //1.2 构建过滤条件
        //1.2.1 构建品牌过滤
        List<Long> brandId = searchParmVo.getBrandId();
        if (!CollectionUtils.isEmpty(brandId)) {
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", brandId));
        }

        //1.2.2构建分类过滤
        List<Long> categoryId = searchParmVo.getCategoryId();
        if (!CollectionUtils.isEmpty(categoryId)) {
            boolQueryBuilder.filter(QueryBuilders.termsQuery("categoryId", categoryId));
        }

        //1.2.3构建价格区间
        Double priceFrom = searchParmVo.getPriceFrom();
        Double priceTo = searchParmVo.getPriceTo();
        if (priceFrom != null || priceTo != null) {
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
            boolQueryBuilder.filter(rangeQuery);
            if (priceFrom != null) {
                rangeQuery.gte(priceFrom);
            }
            if (priceTo != null) {
                rangeQuery.lte(priceTo);
            }

        }

        //1.2.4构建是否有货
        Boolean store = searchParmVo.getStore();
        // 如果请求参数中的store不为空，则就是查询有货的。这里为了方便后续代码演示，可以查询无货
        if (store != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("store", store));
        }

        //1.2.5构建规格参数过滤
        List<String> props = searchParmVo.getProps(); //4:8G-12G
        if (!CollectionUtils.isEmpty(props)) {
            for (String prop : props) {
                //如果当前prop为空,则直接处理下一个
                if (StringUtils.isBlank(prop)) {
                    continue;
                }
                //如果不为空,则将: - 处理分割字符串
                String attrId = StringUtils.substringBefore(prop, ":");
                String attrValue = StringUtils.substringAfter(prop, ":"); // 8G-12G
                if (!StringUtils.isNumeric(attrId) || StringUtils.isBlank(attrValue)) {
                    continue;
                }

                //每一个规格参数过滤,就是一个嵌套过滤
                BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("searchAttrs", boolQuery, ScoreMode.None);
                boolQueryBuilder.filter(nestedQuery);

                boolQuery.must(QueryBuilders.termQuery("searchAttrs.attrId", attrId));
                boolQuery.must(QueryBuilders.termsQuery("searchAttrs.attrValue", StringUtils.split(attrValue, "-")));
            }
        }

        //2.排序条件
        Integer sort = searchParmVo.getSort();
        switch (sort) {
            case 1:
                searchSourceBuilder.sort("price", SortOrder.DESC);
                break;
            case 2:
                searchSourceBuilder.sort("price", SortOrder.ASC);
                break;
            case 3:
                searchSourceBuilder.sort("sales", SortOrder.DESC);
                break;
            case 4:
                searchSourceBuilder.sort("creatTime", SortOrder.DESC);
                break;
            default:
                searchSourceBuilder.sort("_score", SortOrder.DESC);
                break;
        }

        //3.分页条件
        Integer pageNum = searchParmVo.getPageNum();
        Integer pageSize = searchParmVo.getPageSize();
        searchSourceBuilder.from((pageNum - 1) * pageSize);
        searchSourceBuilder.size(pageSize);

        //4.构建高亮
        searchSourceBuilder.highlighter(new HighlightBuilder().field("title").preTags("<font style='color:red;'>").postTags("</font>"));

        //5.构建聚合条件
        //5.1. 品牌聚合
        searchSourceBuilder.aggregation(AggregationBuilders.terms("brandIdAgg").field("brandId").subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName")).subAggregation(AggregationBuilders.terms("logoAgg").field("logo")));

        //5.2. 分类聚合
        searchSourceBuilder.aggregation(AggregationBuilders.terms("categoryIdAgg").field("categoryId").subAggregation(AggregationBuilders.terms("categoryNameAgg").field("categoryName")));

        //5.3. 规格参数聚合
        searchSourceBuilder.aggregation(AggregationBuilders.nested("attrAgg", "searchAttrs").subAggregation(AggregationBuilders.terms("attrIdAgg").field("searchAttrs.attrId").subAggregation(AggregationBuilders.terms("attrNameAgg").field("searchAttrs.attrName")).subAggregation(AggregationBuilders.terms("attrValueAgg").field("searchAttrs.attrValue"))));

        //6. 结果过滤
        searchSourceBuilder.fetchSource(new String[]{"skuId","title","subtitle","price","defaultImage"},null);


        System.out.println(searchSourceBuilder);
        return searchSourceBuilder;
    }
}
